/* ** por compatibilidad se omiten tildes **
================================================================================
 TRABAJO PRACTICO 3 - System Programming - ORGANIZACION DE COMPUTADOR II - FCEN
================================================================================
  definicion de funciones del manejador de memoria
*/
#include "defines.h"
#include "mmu.h"
#include "i386.h"

#define PRESENT		1 << 0
#define R_W		1 << 1
#define SUPERVISOR 	1 << 2              // si esta en 1, todos acceden. en 0, solo el kernel

#define INICIO_DIR_DISPONIBLES 	0x164000
#define MAX_PAGINAS_POR_TAREA 	5

// ----- page on demand -------
unsigned short int number_of_pages_per_task[4] = {0,0,0,0};
unsigned int next_available_page_frame = INICIO_DIR_DISPONIBLES;
unsigned int pd_address_per_task[4] = {
	TASK_1_PAGE_DIR,
	TASK_2_PAGE_DIR,
	TASK_3_PAGE_DIR,
	TASK_4_PAGE_DIR
};
// ------------------------------


extern void test_paging(unsigned int cr3);

void mmu_mapear_pagina(unsigned int virtual, unsigned int cr3, unsigned int fisica, unsigned int attrs) {
	
	unsigned int tableIndex 	= (virtual >> 12) & 0x3ff;
	unsigned int directoryIndex 	= (virtual >> 22) & 0x3ff;

    unsigned int pd_entry = *((unsigned int *) cr3 + directoryIndex);
    
	unsigned int * page_table_ptr = (unsigned int*) (pd_entry & 0xFFFFF000);
	page_table_ptr += tableIndex;
	*page_table_ptr = fisica + attrs;

    tlbflush();
}


void inicializar_directorio(unsigned int pdAddress) {
	int indice = 0;
	unsigned int* pde_ptr = (unsigned int*) pdAddress;
    unsigned int attrs = R_W | SUPERVISOR;
	while (indice < 1024)
	{
		*pde_ptr = 0x0000000 + attrs;
		pde_ptr += 1;
		indice++;
	}
}

void copiar_pagina(unsigned char* origen, unsigned char* destino) {
	int indice = 0;
	
	for(indice=0; indice < 4096; ++indice)
		destino[indice] = origen[indice];

}

void inicializar_paginas_de_kernel(unsigned int* page_table)  {
    int indice = 0;
    unsigned int pt_entry = 0x0000;
    unsigned int attrs = PRESENT | R_W;     // supervisor=0 : solo accede el kernel
    for(indice=0;indice < 356; ++indice) {
        *page_table = pt_entry + attrs;
        pt_entry += 0x1000;
        page_table += 1;
    }
}

void inicializar_paginas_on_demand(unsigned int pdAddress, unsigned int attrs)  {
    int indice = 0;
    unsigned int virtual_address = 0x3d0000;
    unsigned int physical_address = 0x164000;
    for(indice=0;indice < 32; ++indice) {
		    mmu_mapear_pagina(virtual_address, pdAddress, physical_address, attrs);
		    virtual_address += 0x1000;
        
    }
}


void mmu_inicializar_tarea_jugador(unsigned int pdAddress,
					unsigned int* page_table,
					unsigned int stack_pa,
					unsigned int code_pa,
					unsigned char* code_src) {

	inicializar_directorio(pdAddress);
	
    //unsigned int attrs = PRESENT | R_W | SUPERVISOR;
    unsigned int attrs = 7;
    
	unsigned int task_page_table_entry = (unsigned int) page_table + attrs;
	*(unsigned int*)pdAddress = task_page_table_entry;

    // identity mapping para las paginas de kernel
    inicializar_paginas_de_kernel(page_table);
	
    //mapeo pagina de stack
    mmu_mapear_pagina((unsigned int) TASK_STACK, pdAddress, (unsigned int) stack_pa, attrs);
    
	//copio codigo
    copiar_pagina(code_src, (unsigned char*) code_pa);
        
    //mapeo pagina de codigo
    mmu_mapear_pagina((unsigned int) TASK_CODE, pdAddress, (unsigned int) code_pa, attrs);
    
    //mapeo tablero
    mmu_mapear_pagina((unsigned int) TABLERO_ADDR, pdAddress, (unsigned int) TABLERO_ADDR_PA, (unsigned int) PRESENT | SUPERVISOR);
    
    //mapeo pages on demand not present
	//inicializar_paginas_on_demand(pdAddress, (unsigned int) PRESENT | R_W | SUPERVISOR);
}

void mmu_inicializar_tarea_arbitro(unsigned int pdAddress,
					unsigned int* page_table,
					unsigned int stack_pa,
					unsigned int code_pa,
					unsigned char* code_src) {
						
	unsigned int attrs = PRESENT | R_W | SUPERVISOR;

    // inicializo el directorio de paginas
    inicializar_directorio(pdAddress);

    // defino la primera tabla de paginas en el directorio
	unsigned int task_page_table_entry = (unsigned int) page_table + attrs;
	*(unsigned int*)pdAddress = task_page_table_entry;

    // identity mapping para las paginas de kernel
    inicializar_paginas_de_kernel(page_table);

	//mapeo pagina de stack
    mmu_mapear_pagina((unsigned int) TASK_STACK, pdAddress, stack_pa, attrs);

	//copio codigo
    copiar_pagina(code_src, (unsigned char*) code_pa);
	
	//mapeo pagina de codigo
    mmu_mapear_pagina((unsigned int) TASK_CODE, pdAddress, code_pa, attrs);

	//mapeo tablero
    mmu_mapear_pagina((unsigned int) TABLERO_ADDR,
                        pdAddress, 
                        (unsigned int) TABLERO_ADDR_PA,
                        (unsigned int) PRESENT | SUPERVISOR);
	
	//identity mapping para la memoria de video.
	mmu_mapear_pagina(
                    (unsigned int) VIDEO_ADDR,
                    pdAddress, 
                    (unsigned int) VIDEO_ADDR,
                    (unsigned int) PRESENT | R_W | SUPERVISOR
    );

}


void mmu_inicializar() {
	
	mmu_inicializar_tarea_jugador(
        (unsigned int)TASK_1_PAGE_DIR,
		(unsigned int*)TASK_1_PAGE_TABLE,
		(unsigned int)TASK_1_STACK_PA,
		(unsigned int)TASK_1_CODE_PA,
		(unsigned char*)TASK_1_CODE_SRC_ADDR
	);
	mmu_inicializar_tarea_jugador(
        (unsigned int)TASK_2_PAGE_DIR,
		(unsigned int*)TASK_2_PAGE_TABLE,
		(unsigned int)TASK_2_STACK_PA,
		(unsigned int)TASK_2_CODE_PA,
		(unsigned char*)TASK_2_CODE_SRC_ADDR
	);
	mmu_inicializar_tarea_jugador(
        (unsigned int)TASK_3_PAGE_DIR,
		(unsigned int*)TASK_3_PAGE_TABLE,
		(unsigned int)TASK_3_STACK_PA,
		(unsigned int)TASK_3_CODE_PA,
		(unsigned char*)TASK_3_CODE_SRC_ADDR
	);
	mmu_inicializar_tarea_jugador(
        (unsigned int)TASK_4_PAGE_DIR,
		(unsigned int*)TASK_4_PAGE_TABLE,
		(unsigned int)TASK_4_STACK_PA,
		(unsigned int)TASK_4_CODE_PA,
		(unsigned char*)TASK_4_CODE_SRC_ADDR
	);
	
	mmu_inicializar_tarea_arbitro(
        (unsigned int)TASK_5_PAGE_DIR,
		(unsigned int*)TASK_5_PAGE_TABLE,
		(unsigned int)TASK_5_STACK_PA,
		(unsigned int)TASK_5_CODE_PA,
		(unsigned char*)TASK_5_CODE_SRC_ADDR
	);

    //mapeo pages on demand not present
	//inicializar_paginas_on_demand(KERNEL_PAGE_DIR, (unsigned int) R_W);

    
	//test_paging((unsigned int) TASK_5_PAGE_DIR);
}


unsigned int mmu_handle_page_fault(unsigned int address, unsigned int task_id) {
	int return_code = 1; // success
	unsigned int number_of_pages = number_of_pages_per_task[task_id -1];
	unsigned int pd_address = pd_address_per_task[task_id -1];
	
	if (address >= 0x3D0000 && address < 0x3F0000) {
		if (number_of_pages < 5) {
			mmu_mapear_pagina(
						address,
						pd_address, 
						next_available_page_frame,
						(unsigned int) PRESENT | R_W | SUPERVISOR
			);
			
			next_available_page_frame += 0x1000;
			number_of_pages_per_task[task_id -1] += 1;
		
		
		} else {
			return_code = 0; // failure
		}
	} else {
		return_code = 2; //failure: invalid virtual address
	}

	return return_code;
}
